package sk.naive.talker.message;

import sk.naive.talker.util.*;
import sk.naive.talker.*;
import sk.naive.talker.adapter.TagConsts;
import sk.naive.talker.props.SexProperty;

import java.util.*;
import java.util.regex.*;
import java.text.SimpleDateFormat;

/**
 * Default implementation of MessageFactory interface.
 * <p>
 * MessageFactory construction requires resource bundle key (String).
 * Then you can create message objects with property key and context (Map).
 * Values in context are used in the construction of the message from its
 * general form (patterns with {...} are replaced).
 *
 * @author <a href="mailto:virgo@naive.deepblue.sk">Richard "Virgo" Richter</a>
 * @version $Revision: 1.14 $ $Date: 2005/01/18 20:51:30 $
 */
public class DefaultMessageFactory
	implements ReloadableMessageFactory
{
	// TODO neviem ci sem maju ist tieto konstanty
	public static final String LANG_EN = "en";
	public static final String LANG_SK = "sk";

	public static final String CTXKEY_VAL = "ctxVal";
	public static final String CTXKEY_LANG = User.UPROP_LANG;

	private static final Map<String,Locale> languageLocale;
	private static final Pattern templateTag = Pattern.compile("\\{(.+?)(?:\\((.+?)\\))?\\}");
	private static final Map<String,Message.Replacer> tagReplacer;

	private Map<Locale,ResourceBundle> localeResource;
	private String resourceName;
	private ClassLoader loader;

	public DefaultMessageFactory(String resourceName) {
		localeResource = new HashMap<Locale,ResourceBundle>();
		this.resourceName = resourceName;
		reload();
	}

	public void reload() {
		loader = NondelegatingClassLoader.create(null);
		for (Locale l : languageLocale.values()) {
			localeResource.put(l, ResourceBundle.getBundle(resourceName, l, loader));
		}
	}

	public String process(String template, Map ctx, Locale locale) {
		StringBuilder sb = new StringBuilder();
		Matcher m = templateTag.matcher(template);
		int lastEnd = 0;
		while (m.find()) {
			Message.Replacer r = (Message.Replacer) tagReplacer.get(m.group(1));
			if (r == null) {
				continue;
			}
			sb.append(template.substring(lastEnd, m.start()));
			sb.append(r.replace(this, m.group(2), ctx, locale));
			lastEnd = m.end();
		}
		sb.append(template.substring(lastEnd));
		return sb.toString();
	}

	/**
	 * Returns template string with fallback to default locale.
	 *
	 * @param l requested locale
	 * @param key message key in the resource bundle
	 * @return template string from the bundle
	 * @throws MissingResourceException thrown if message for the specified key
	 * is not found neither in the bundle for locale nor in the bundle
	 * for default locale
	 */
	private String getTemplate(Locale l, String key) {
		try {
			return getResource(l).getString(key);
		} catch (MissingResourceException e) {
			if (l == Locale.getDefault()) {
				throw e;
			} else {
				return getResource(Locale.getDefault()).getString(key);
			}
		}
	}

	public Message createMessage(String key, Map ctx) {
		class MessageImpl implements Message {
			private Map localizedMessages;
			private long timestamp;

			public MessageImpl(String key, Map ctx) {
				localizedMessages = new HashMap();
				Iterator i = getAllLanguages().iterator();
				while (i.hasNext()) {
					Locale l = (Locale) languageLocale.get(i.next());
					localizedMessages.put(l, process(getTemplate(l, key), ctx, l));
				}
				timestamp = System.currentTimeMillis();
			}

			public Date getDate() {
				return new Date(timestamp);
			}

			public String get(String lang) {
				return get(getLocale(lang));
			}

			public String get(Locale l) {
				if (l == null) {
					l = Locale.getDefault();
				}
				return (String) localizedMessages.get(l);
			}
		};
		return new MessageImpl(key, ctx);
	}

	public String getString(String key, Map ctx, Locale l) {
		if (l == null) {
			l = Locale.getDefault();
		}
		return process(getTemplate(l, key), ctx, l);
	}

	public String getString(String key, Map ctx) {
		return getString(key, ctx, (String) ctx.get(CTXKEY_LANG));
	}

	public String getString(String key, Map ctx, String lang) {
		Locale l = (Locale) languageLocale.get(lang);
		return getString(key, ctx, l);
	}

	public String getDefaultLanguage() {
		return LANG_EN;
	}

	public Set getAllLanguages() {
		return languageLocale.keySet();
	}

	public Locale getLocale(String lang) {
		return (Locale) languageLocale.get(lang);
	}

	private ResourceBundle getResource(Locale l) {
		return (ResourceBundle) localeResource.get(l);
	}

	public static void registerReplacer(String key, Message.Replacer r) {
		tagReplacer.put(key, r);
	}

	private static String colourTag(String colour) {
		if (colour == null) {
			return Utils.tag(TagConsts.CLASS, "normal");
		}
		return Utils.tag(TagConsts.CLASS, colour);
	}

	private static final SimpleDateFormat sdfDate = new SimpleDateFormat("d. M. yyyy, HH:mm");
	static {
		Locale l = new Locale("en", "US");
		Locale.setDefault(l);
		languageLocale = new HashMap<String,Locale>();
		languageLocale.put(LANG_EN, l);

		l = new Locale("sk", "SK");
		languageLocale.put(LANG_SK, l);

		tagReplacer = new HashMap<String,Message.Replacer>();
		// TODO oddelit tieto replacery (konkretne) niekam inam :-)
		registerReplacer("userLogin", new Message.Replacer() {
			public String replace(MessageFactory factory, String params, Map ctx, Locale locale) {
				return (String) ctx.get(User.UPROP_LOGIN);
			}
		});
		registerReplacer("userName", new Message.Replacer() {
			public String replace(MessageFactory factory, String params, Map ctx, Locale locale) {
				return colourTag((String) ctx.get(User.UPROP_MYCOLOUR)) + (String) ctx.get(User.UPROP_NAME);
			}
		});
		registerReplacer("nameForm", new Message.Replacer() {
			public String replace(MessageFactory factory, String params, Map ctx, Locale locale) {
				// TODO mozno ina premenna (NameForm.UPROP..., ale to je zase command ;-))
				String form = (String) ctx.get(User.UPROP_LANG + "." + params);
				if (form == null) {
					form = (String) ctx.get(User.UPROP_NAME);
				}
				return colourTag((String) ctx.get(User.UPROP_MYCOLOUR)) + form;
			}
		});
		registerReplacer("prop", new Message.Replacer() {
			public String replace(MessageFactory factory, String params, Map ctx, Locale locale) {
				Object val = ctx.get(params);
				if (val == null) {
					return "";
				}
				return val.toString();
			}
		});
		registerReplacer("ifProp", new Message.Replacer() {
			public String replace(MessageFactory factory, String params, Map ctx, Locale locale) {
				String[] sa = params.split(",", 3);
				if (sa.length > 1) {
					Object val = ctx.get(sa[0]);
					if (val != null) {
						return factory.getString(sa[1], ctx, locale);
					}
					if (sa.length > 2) {
						return factory.getString(sa[2], ctx, locale);
					}
				}
				return "";
			}
		});
		registerReplacer("include", new Message.Replacer() {
			public String replace(MessageFactory factory, String params, Map ctx, Locale locale) {
				if (params != null) {
					return factory.getString(params, ctx, locale);
				}
				else {
					return "";
				}
			}
		});
		registerReplacer("userSex", new Message.Replacer() {
			public String replace(MessageFactory factory, String params, Map ctx, Locale locale) {
				String[] sa = params.split("\\|");
				String sex = (String) ctx.get(SexProperty.UPROP_SEX);
				if (sex == null) {
					sex = "m";
				}
				char f = sex.charAt(0);
				if (f == 'm' || f == 'M') {
					return sa[0];
				}
				return sa[1];
			}
		});
		registerReplacer("val", new Message.Replacer() {
			public String replace(MessageFactory factory, String params, Map ctx, Locale locale) {
				String key = CTXKEY_VAL;
				if (params != null) {
					key += params;
				}
				Object val = ctx.get(key);
				if (val != null) {
					return val.toString();
				}
				return "";
			}
		});
		registerReplacer("date", new Message.Replacer() {
			public String replace(MessageFactory factory, String params, Map ctx, Locale locale) {
				String key = CTXKEY_VAL;
				SimpleDateFormat sdf = sdfDate;
				if (params != null) {
					String[] sa = params.split(",", 2);
					key += sa[0];
					if (sa.length > 1) {
						sdf = new SimpleDateFormat(sa[1]);
					}
				}
				Date d = (Date) ctx.get(key);
				if (d == null) {
					return "?";
				}
				return sdf.format(d);
			}
		});
		registerReplacer("numSelector", new Message.Replacer() {
			public String replace(MessageFactory factory, String params, Map ctx, Locale locale) {
				String key = CTXKEY_VAL;
				String[] sa = params.split(",", -1);
				key += sa[0];
				int value = 0;
				if (ctx.get(key) instanceof Number) {
					value = ((Number) ctx.get(key)).intValue();
				} else if (ctx.get(key) instanceof String) {
					value = Integer.parseInt((String) ctx.get(key));
				}
				String valueStr = "?";
				if (ctx.get(key) != null) {
					valueStr = String.valueOf(value);
				}
				String retVal = null;
				for (int i = 1; i < sa.length; i++) {
					String[] expr = sa[i].split("=", 2);
					if (expr.length == 1) {
						retVal = expr[0];
					} else {
						String[] range = expr[0].split("-");
						if (range.length == 1) {
							if (Integer.parseInt(range[0]) == value) {
								retVal = expr[1];
								break;
							}
						} else {
							if (Integer.parseInt(range[0]) <= value && Integer.parseInt(range[1]) >= value) {
								retVal = expr[1];
								break;
							}
						}
					}
				}
				return retVal.replaceAll("#", valueStr);
			}
		});
		registerReplacer("subCtx", new Message.Replacer() {
			public String replace(MessageFactory factory, String params, Map ctx, Locale locale) {
				String[] sa = params.split(",", 3);
				Map subCtx = (Map) ctx.get(CTXKEY_VAL + sa[0]);
				Message.Replacer r = (Message.Replacer) tagReplacer.get(sa[1]);
				if (r == null || subCtx == null) {
					return null;
				}
				String param = null;
				if (sa.length == 3) {
					param = sa[2];
				}
				return r.replace(factory, param, subCtx, locale);
			}
		});
	}
}
